/**
 * 
 */
package history;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.NoSuchElementException;

import common.Data;
import common.Measurment;

/**
 * Basic implementation for historic data handling.
 * 
 * Uses linked list as data holding structure.
 * 
 * @author Kamil "Mamut" Balwierz <kamil.balwierz@gmail.com>
 */
public class BasicDataHistory implements DataHistory {

	/**
	 * Creates empty data history
	 * 
	 */
	public BasicDataHistory() {
		lista = new LinkedList<Data>();
		latest = new Date(0);
		listeners = new LinkedList<DataHistoryListener>();
	}
	
	/**
	 * Creates new instance of data history filled with data form file
	 * 
	 * @param file file with data history to load
	 * @throws IOException dude file handling is nasty... like public hot-spot
	 */
	public BasicDataHistory(File file) throws IOException {
		lista = new LinkedList<Data>();
		latest = new Date(0);
		listeners = new LinkedList<DataHistoryListener>();
		load(file);
	}
	
	/**
	 * 
	 * 
	 * @param file
	 * @return
	 * @throws IOException
	 */
	protected InputStream prepareReadFile(File file) throws IOException {
		return new BufferedInputStream(new FileInputStream(file));
	}
	
	/**
	 * 
	 * 
	 * @param file
	 * @return
	 * @throws IOException
	 */
	protected OutputStream preapareWriteFile(File file) throws IOException {
		return new BufferedOutputStream(new FileOutputStream(file));
	}
	
	/**
	 * 
	 * 
	 * @param file
	 * @return
	 * @throws IOException
	 */
	protected int readFile(InputStream file) throws IOException {
		ObjectInputStream in = new ObjectInputStream(file);
		int result = 0;
		Data ob;
		Date data = new Date(0);
		LinkedList<Data> temp = new LinkedList<Data>();
		while(file.available()>0) {
			try {
				ob = (Data)in.readObject();
				temp.add(ob);
				if(data.compareTo(ob.getDate()) < 0) {
					data = ob.getDate();
				}
			} catch (ClassNotFoundException e) {
				return 0;
			}
		}
		in.close();
		result = temp.size();
		lista.addAll(temp);
		if(latest.compareTo(data) <0) {
			latest=data;
		}
		Collections.sort(lista, new ByDateOrdering());
		return result;
	}
	
	/**
	 * 
	 * 
	 * @param cout
	 * @throws IOException
	 */
	protected void writeFile(OutputStream cout) throws IOException {
		ObjectOutputStream out = new ObjectOutputStream(cout);
		ListIterator<Data> it = iteratorFront();
		while(it.hasNext()) {
			out.writeObject(it.next());
		}
		out.close();
	}
	
	protected void writeTextFile(OutputStream cout) throws IOException {
		DataOutputStream out = new DataOutputStream(cout);
		out.flush();
		ListIterator<Data> it = iteratorFront();
		while(it.hasNext()) {
			Data current = it.next();
			out.writeBytes(current.getDate().toString()+ " " + current.getTemperature() + "\n");
		}
		out.close();
	}
	
	/**
	 * 
	 * 
	 * @param file
	 * @throws IOException
	 */
	protected void closeReadFile(InputStream file) throws IOException {
		file.close();
	}
	
	/**
	 * 
	 * 
	 * @param file
	 * @throws IOException
	 */
	protected void closeWriteFile(OutputStream file) throws IOException {
		file.close();
	}
	
	/* (non-Javadoc)
	 * @see history.DataHistory#add(common.Data)
	 */
	@Override
	public void add(Data data) {
		lista.add(data);
		if(latest.compareTo(data.getDate()) > 0) {
			Collections.sort(lista, new ByDateOrdering());
		} else {
			latest = data.getDate();
		}
		notifyIncomingData();
	}

	/* (non-Javadoc)
	 * @see history.DataHistory#iteratorByDate(java.util.Date)
	 */
	@Override
	public ListIterator<Data> iteratorByDate(Date data) {
		Data temp = new Measurment(data, 0);
		int pos = Collections.binarySearch(lista, temp, new ByDateOrdering());
		if(pos<0) 
			pos = -pos;
		return lista.listIterator(pos);
	}

	/* (non-Javadoc)
	 * @see history.DataHistory#iteratorFront()
	 */
	@Override
	public ListIterator<Data> iteratorFront() {
		return lista.listIterator();
	}

	/* (non-Javadoc)
	 * @see history.DataHistory#elementByDate(java.util.Date)
	 */
	@Override
	public Data elementByDate(Date data) throws NoSuchElementException {
		Data temp = new Measurment(data, 0);
		int pos = Collections.binarySearch(lista, temp, new ByDateOrdering());
		if(pos < 0)
			throw new NoSuchElementException();
		return lista.get(pos);
	}

	/* (non-Javadoc)
	 * @see history.DataHistory#elementFirst()
	 */
	@Override
	public Data elementFirst() throws NoSuchElementException {
		return lista.getFirst();
	}

	/* (non-Javadoc)
	 * @see history.DataHistory#elementLast()
	 */
	@Override
	public Data elementLast()  throws NoSuchElementException {
		return lista.getLast();
	}

	/* (non-Javadoc)
	 * @see history.DataHistory#load(java.io.File)
	 */
	@Override
	public int load(File file) throws IOException {
		InputStream in = prepareReadFile(file);
		int result = readFile(in);
		closeReadFile(in);
		if(result>0)
			notifyIncomingData();
		return result;
	}

	/* (non-Javadoc)
	 * @see history.DataHistory#save(java.io.File)
	 */
	@Override
	public void save(File file) throws IOException {
		OutputStream out = preapareWriteFile(file);
		writeTextFile(out);
		closeWriteFile(out);
	}
	
	/* (non-Javadoc)
	 * @see history.DataHistory#iteratorEnd()
	 */
	@Override
	public ListIterator<Data> iteratorEnd() {
		return lista.listIterator(lista.size());
	}
	
	/* (non-Javadoc)
	 * @see history.DataHistory#getCount()
	 */
	@Override
	public int getCount() {
		return lista.size();
	}
	
	private LinkedList<Data> lista;
	private Date latest;
	private LinkedList<DataHistoryListener> listeners;
	
	@Override
	public void registerListener(DataHistoryListener listener) {
		listeners.add(listener);
	}
	
	protected void notifyIncomingData() {
		for (DataHistoryListener listener : listeners) {
			listener.notifyIncomingData();
		}
	}

}
